Apgūstiet vispārīgo apmeklētāja dizaina modeli koku pārejai. Visaptverošs ceļvedis algoritmu atdalīšanai no koku struktūrām, lai nodrošinātu elastīgāku un vieglāk uzturamu kodu.
Elpojoša koku pārejas atslēgšana: Padziļināta vispārīgā apmeklētāja dizaina modeļa izpēte
Programmatūras inženierijas pasaulē mēs bieži saskaramies ar datiem, kas organizēti hierariskās, koku līdzīgās struktūrās. Sākot ar abstraktās sintakses kokiem (AST), ko kompilatori izmanto, lai saprastu mūsu kodu, līdz pat Document Object Model (DOM), kas darbina internetu, un pat vienkāršām failu sistēmām, koki ir visur. Pamata uzdevums, strādājot ar šīm struktūrām, ir pāreja: katra mezgla apmeklēšana, lai veiktu kādu operāciju. Tomēr izaicinājums ir to izdarīt tīrā, viegli uzturētā un paplašināmā veidā.
Tradicionālās pieejas bieži vien iegulda darbības loģiku tieši mezglu klasēs. Tas noved pie monolītiska, cieši savienota koda, kas pārkāpj programmatūras dizaina pamatprincipus. Jaunas operācijas pievienošana, piemēram, skaista drukātāja vai validatora, liek jums modificēt katru mezglu klasi, padarot sistēmu trauslu un grūti uzturamu.
Klasiskais apmeklētāja dizaina modelis piedāvā spēcīgu risinājumu, atdalot algoritmus no objektiem, uz kuriem tie darbojas. Taču pat klasiskajam modelim ir savi ierobežojumi, īpaši attiecībā uz paplašināmību. Šeit Vispārīgais apmeklētāja dizaina modelis, īpaši, ja tas tiek piemērots koku pārejai, parādās pats par sevi. Izmantojot modernās programmēšanas valodu iespējas, piemēram, vispārīgās izpausmes, veidnes un variantus, mēs varam izveidot ļoti elastīgu, atkārtoti lietojamu un jaudīgu sistēmu jebkuras koku struktūras apstrādei.
Šī padziļinātā izpēte palīdzēs jums ceļā no klasiskā apmeklētāja dizaina modeļa līdz sarežģītai, vispārīgai implementācijai. Mēs izpētīsim:
- Atgādinājums par klasisko apmeklētāja dizaina modeli un tā dabiskajiem izaicinājumiem.
- Evolūcija uz vispārīgu pieeju, kas vēl vairāk atdala operācijas.
- Detalizēta, soli pa solim vispārīga koku pārejas apmeklētāja implementācija.
- Būtiskas priekšrocības, atdalot pārejas loģiku no darbības loģikas.
- Reālās pasaules lietojumprogrammas, kurās šis modelis sniedz milzīgu vērtību.
Neatkarīgi no tā, vai jūs veidojat kompilatoru, statiskās analīzes rīku, UI sistēmu vai jebkuru sistēmu, kas balstās uz sarežģītām datu struktūrām, šī modeļa apgūšana paaugstinās jūsu arhitektūras domāšanu un koda kvalitāti.
Atkārtoti aplūkojam klasisko apmeklētāja dizaina modeli
Pirms mēs varam novērtēt vispārīgo evolūciju, mums ir stingri jāsaprot tā pamats. Apmeklētāja dizaina modelis, kā to aprakstījuši "Gang of Four" savā seminalajā grāmatā Design Patterns: Elements of Reusable Object-Oriented Software, ir uzvedības modelis, kas ļauj pievienot jaunas operācijas esošajām objektu struktūrām, nemodificējot tās.
Problēma, ko tas atrisina
Iedomājieties, ka jums ir vienkāršs aritmētisks izteiksmes koks, kas sastāv no dažādiem mezglu tipiem, piemēram, NumberNode (burtisks vērtība) un AdditionNode (attēlo divu apakšizteikumu saskaitīšanu). Jūs, iespējams, vēlētos veikt vairākas atšķirīgas operācijas ar šo koku:
- Novērtēšana: Aprēķiniet izteiksmes gala skaitlisko rezultātu.
- Skaista drukāšana: Generējiet cilvēkam salasāmu virknes attēlojumu, piemēram, "(5 + 3)".
- Tipu pārbaude: Pārbaudiet, vai operācijas ir derīgas attiecīgajiem tipiem.
Naivā pieeja būtu pievienot metodes, piemēram, `evaluate()`, `print()` un `typeCheck()`, bāzes klasei `Node` un pārrakstīt tās katrā konkrētajā mezglu klasē. Tas palielina mezglu klašu apjomu ar nesaistītu loģiku. Katru reizi, kad jūs izdomājat jaunu operāciju, jums ir jāpieskaras katrai mezglu klasei hierarhijā. Tas pārkāpj Atklātības/Slēgtības principu, kas nosaka, ka programmatūras vienībām ir jābūt atklātām paplašināšanai, bet slēgtām modifikācijai.
Klasiskais risinājums: Dubultā izsaukšana
Apmeklētāja dizaina modelis atrisina šo problēmu, ieviešot divas jaunas hierarḥijas: Apmeklētāja hierarhiju un Elementa hierarhiju (mūsu mezgli). Burvība slēpjas tehnikā, ko sauc par dubulto izsaukšanu.
Galvenie spēlētāji ir:
- Elementa interfeiss (piemēram, `Node`): Definē metodi `accept(Visitor v)`.
- Konkrētie elementi (piemēram, `NumberNode`, `AdditionNode`): Ievieš `accept` metodi. Implementācija ir vienkārša: `visitor.visit(this);`.
- Apmeklētāja interfeiss: Deklarē pārslodzītu `visit` metodi katram konkrētajam elementa tipam. Piemēram, `visit(NumberNode n)` un `visit(AdditionNode n)`.
- Konkrētais apmeklētājs (piemēram, `EvaluationVisitor`, `PrintVisitor`): Ievieš `visit` metodes, lai veiktu konkrētu operāciju.
Lūk, kā tas darbojas: jūs izsaucat `node.accept(myVisitor)`. `accept` iekšienē mezgls izsauc `myVisitor.visit(this)`. Šajā brīdī kompilators zina `this` konkrēto tipu (piemēram, `AdditionNode`) un `myVisitor` konkrēto tipu (piemēram, `EvaluationVisitor`). Tāpēc tas var pāriet uz pareizo `visit` metodi: `EvaluationVisitor::visit(AdditionNode*)`. Šis divpakāpju izsaukums panāk to, ko viens virtuālais funkcijas izsaukums nespēj: pareizās metodes noteikšanu, pamatojoties uz divu dažādu objektu darbības laika tipiem.
Klasiskā modeļa ierobežojumi
Lai gan elegants, klasiskajam apmeklētāja dizaina modelim ir ievērojams trūkums, kas kavē tā izmantošanu mainīgās sistēmās: elementu hierarhijas stingrība.
`Visitor` interfeiss satur `visit` metodi katram `ConcreteElement` tipam. Ja vēlaties pievienot jaunu mezglu tipu — piemēram, `MultiplicationNode` — jums ir jāpievieno jauna `visit(MultiplicationNode n)` metode bāzes `Visitor` interfeisam. Tas liek jums atjaunināt katru esošo konkrēto apmeklētāja klasi savā sistēmā, lai ieviestu šo jauno metodi. Problēma, ko mēs atrisinājām, pievienojot jaunas operācijas, tagad parādās atkārtoti, pievienojot jaunus elementu tipus. Sistēma ir slēgta modifikācijai operāciju pusē, bet plaši atvērta elementu pusē.
Šī cikliskā atkarība starp elementu hierarhiju un apmeklētāja hierarhiju ir galvenais motīvs meklēt elastīgāku, vispārīgu risinājumu.
Vispārīgā evolūcija: Elastīgāka pieeja
Klasiskā modeļa galvenais ierobežojums ir statiskā, kompilācijas laika saite starp apmeklētāja interfeisu un konkrētajiem elementu tipiem. Vispārīgā pieeja cenšas šo saiti pārtraukt. Galvenā ideja ir pārvietot atbildību par pāreju uz pareizo apstrādes loģiku prom no stingras pārslodzītu metožu saskarnes.
Modernais C++, ar savu jaudīgo veidņu metaprogrammēšanu un standarta bibliotēkas funkcijām, piemēram, `std::variant`, nodrošina ārkārtīgi tīru un efektīvu veidu, kā to ieviest. Līdzīga pieeja var tikt sasniegta valodās, piemēram, C# vai Java, izmantojot atspoguļošanu vai vispārīgās saskarnes, lai gan ar potenciālām veiktspējas kompromisiem.
Mūsu mērķis ir izveidot sistēmu, kurā:
- Jaunu mezglu tipu pievienošana ir lokalizēta un neprasa izmaiņu kaskādi visās esošajās apmeklētāju implementācijās.
- Jaunu operāciju pievienošana paliek vienkārša, saskaņojoties ar sākotnējo apmeklētāja dizaina modeļa mērķi.
- Pārejas loģika pati par sevi (piemēram, pirms secības, pēc secības) var tikt definēta vispārīgi un atkārtoti lietota jebkurai operācijai.
Šis trešais punkts ir mūsu "Koku pārejas tipa implementācijas" atslēga. Mēs ne tikai atdalīsim operāciju no datu struktūras, bet arī pārejas aktu no operācijas akta.
Vispārīgā apmeklētāja implementācija koku pārejai C++
Mēs izmantosim modernu C++ (C++17 vai jaunāku), lai izveidotu mūsu vispārīgo apmeklētāju sistēmu. `std::variant`, `std::unique_ptr` un veidņu kombinācija nodrošina tipu drošu, efektīvu un ļoti izteiksmīgu risinājumu.
1. darbība: Koku mezglu struktūras definēšana
Vispirms definēsim mūsu mezglu tipus. Tā vietā, lai izmantotu tradicionālu mantojuma hierarhiju ar virtuālu `accept` metodi, mēs definēsim mūsu mezglus kā vienkāršus struktūras. Mēs pēc tam izmantosim `std::variant`, lai izveidotu summas tipu, kas var saturēt jebkuru no mūsu mezglu tipiem.
Lai atļautu rekursīvu struktūru (koku, kur mezgli satur citus mezglus), mums ir nepieciešams starpniecības slānis. `Node` struktūra iesaiņos variantu un izmantos `std::unique_ptr` saviem bērniem.
Fails: `Nodes.h`
#include <memory> #include <variant> #include <vector> // Pirmā priekšējā deklarācija galvenajam Node iesaiņojumam struct Node; // Konkrētos mezglu tipus definējam kā vienkāršus datu agregātus struct NumberNode { double value; }; struct BinaryOpNode { enum class Operator { Add, Subtract, Multiply, Divide }; Operator op; std::unique_ptr<Node> left; std::unique_ptr<Node> right; }; struct UnaryOpNode { enum class Operator { Negate }; Operator op; std::unique_ptr<Node> operand; }; // Izmantojam std::variant, lai izveidotu visu iespējamo mezglu tipu summas tipu using NodeVariant = std::variant<NumberNode, BinaryOpNode, UnaryOpNode>; // Galvenā Node struktūra, kas iesaiņo variantu struct Node { NodeVariant var; };
Šī struktūra jau ir milzīgs uzlabojums. Mezglu tipi ir vienkāršas datu struktūras. Viņiem nav zināšanu par apmeklētājiem vai jebkādām operācijām. Lai pievienotu `FunctionCallNode`, vienkārši definējiet struktūru un pievienojiet to `NodeVariant` aliasam. Tas ir vienīgais modifikācijas punkts pašai datu struktūrai.
2. darbība: Vispārīga apmeklētāja izveide ar `std::visit`
`std::visit` utilīta ir šī modeļa stūrakmens. Tā ņem izsaucamo objektu (piemēram, funkciju, lambda vai objektu ar `operator()`) un `std::variant`, un izsauc izsaucamo pareizo pārslodzi, pamatojoties uz variantā pašlaik aktīvo tipu. Tas ir mūsu tipu drošais, kompilācijas laika dubultās izsaukšanas mehānisms.
Apmeklētājs tagad ir vienkārši struktūra ar pārslodzītu `operator()` katram variantā esošajam tipam.
Izveidosim vienkāršu Pretty-Printer apmeklētāju, lai to redzētu darbībā.
Fails: `PrettyPrinter.h`
#include "Nodes.h" #include <string> #include <iostream> struct PrettyPrinter { // Pārslodze NumberNode void operator()(const NumberNode& node) const { std::cout << node.value; } // Pārslodze UnaryOpNode void operator()(const UnaryOpNode& node) const { std::cout << "(- "; std::visit(*this, node.operand->var); // Rekursīva vizīte std::cout << ")"; } // Pārslodze BinaryOpNode void operator()(const BinaryOpNode& node) const { std::cout << "("; std::visit(*this, node.left->var); // Rekursīva vizīte switch (node.op) { case BinaryOpNode::Operator::Add: std::cout << " + "; break; case BinaryOpNode::Operator::Subtract: std::cout << " - "; break; case BinaryOpNode::Operator::Multiply: std::cout << " * "; break; case BinaryOpNode::Operator::Divide: std::cout << " / "; break; } std::visit(*this, node.right->var); // Rekursīva vizīte std::cout << ")"; } };
Pamanāt, kas šeit notiek. Pārejas loģika (bērnu apmeklēšana) un darbības loģika (iekavu un operatoru drukāšana) ir sajauktas `PrettyPrinter` iekšienē. Tas ir funkcionāli, bet mēs varam darīt vēl labāk. Mēs varam atdalīt ko no kā.
3. darbība: Šova zvaigzne — vispārīgais koku pārejas apmeklētājs
Tagad mēs iepazīstinām ar galveno koncepciju: atkārtoti lietojamu `TreeWalker`, kas iekapsulē pārejas stratēğiju. Šis `TreeWalker` pats būs apmeklētājs, taču tā vienīgais uzdevums ir pāriet pa koku. Tas pieņems citas funkcijas (lambdas vai funkciju objektus), kas tiek izpildītas noteiktos pārejas punktos.
Mēs varam atbalstīt dažādas stratēğijas, taču izplatīta un jaudīga ir āķu nodrošināšana "pirms apmeklējuma" (pirms bērnu apmeklēšanas) un "pēc apmeklējuma" (pēc bērnu apmeklēšanas). Tas tieši atbilst pirms secības un pēc secības pārejas darbībām.
Fails: `TreeWalker.h`
#include "Nodes.h" #include <functional> template <typename PreVisitAction, typename PostVisitAction> struct TreeWalker { PreVisitAction pre_visit; PostVisitAction post_visit; // Bāzes gadījums mezgliem bez bērniem (termināļiem) void operator()(const NumberNode& node) { pre_visit(node); post_visit(node); } // Gadījums mezgliem ar vienu bērnu void operator()(const UnaryOpNode& node) { pre_visit(node); std::visit(*this, node.operand->var); // Rekursija post_visit(node); } // Gadījums mezgliem ar diviem bērniem void operator()(const BinaryOpNode& node) { pre_visit(node); std::visit(*this, node.left->var); // Rekursija pa kreisi std::visit(*this, node.right->var); // Rekursija pa labi post_visit(node); } }; // Palīgfunkcija, lai atvieglotu pārejas izveidi template <typename Pre, typename Post> auto make_tree_walker(Pre pre, Post post) { return TreeWalker<Pre, Post>{pre, post}; }
Šis `TreeWalker` ir atdalīšanas šedevrs. Tas neko nezina par drukāšanu, novērtēšanu vai tipu pārbaudi. Tā vienīgais mērķis ir veikt dziļuma pirmās secības pāreju pa koku un izsaukt sniegtos āķus. `pre_visit` darbība tiek izpildīta pirms secības, un `post_visit` darbība tiek izpildīta pēc secības. Izvēloties, kuru lambda ieviest, lietotājs var veikt jebkāda veida operācijas.
4. darbība: `TreeWalker` izmantošana jaudīgām, atdalītām operācijām
Tagad pārveidosim mūsu `PrettyPrinter` un izveidosim `EvaluationVisitor`, izmantojot mūsu jauno vispārīgo `TreeWalker`. Darbības loģika tagad tiks izteikta kā vienkāršas lambdas.
Lai pārsūtītu stāvokli starp lambda izsaukumiem (piemēram, novērtēšanas steku), mēs varam tvert mainīgos pēc atsauces.
Fails: `main.cpp`
#include "Nodes.h" #include "TreeWalker.h" #include <iostream> #include <string> #include <vector> // Palīgs vispārīgas lambdas izveidei, kas var apstrādāt jebkuru mezglu tipu template<class... Ts> struct Overloaded : Ts... { using Ts::operator()...; }; template<class... Ts> Overloaded(Ts...) -> Overloaded<Ts...>; int main() { // Uzbūvēsim koku izteiksmei: (5 + (10 * 2)) auto num5 = std::make_unique<Node>(Node{NumberNode{5.0}}); auto num10 = std::make_unique<Node>(Node{NumberNode{10.0}}); auto num2 = std::make_unique<Node>(Node{NumberNode{2.0}}); auto mult = std::make_unique<Node>(Node{BinaryOpNode{ BinaryOpNode::Operator::Multiply, std::move(num10), std::move(num2) }}); auto root = std::make_unique<Node>(Node{BinaryOpNode{ BinaryOpNode::Operator::Add, std::move(num5), std::move(mult) }}); std::cout << "--- Skaistas drukāšanas operācija --- "; auto printer_pre_visit = Overloaded { [](const NumberNode& node) { std::cout << node.value; }, [](const UnaryOpNode&) { std::cout << "(- "; }, [](const BinaryOpNode&) { std::cout << "("; } }; auto printer_post_visit = Overloaded { [](const NumberNode&) {}, // Nedara neko [](const UnaryOpNode&) { std::cout << ")"; }, [](const BinaryOpNode& node) { switch (node.op) { case BinaryOpNode::Operator::Add: std::cout << " + "; break; case BinaryOpNode::Operator::Subtract: std::cout << " - "; break; case BinaryOpNode::Operator::Multiply: std::cout << " * "; break; case BinaryOpNode::Operator::Divide: std::cout << " / "; break; } } }; // Tas nedarbosies, jo bērni tiek apmeklēti starp pirms un pēc. // Precizēsim pāreju, lai tā būtu elastīgāka, lai drukātu pirms secības. // Labāka pieeja skaistai drukāšanai ir "vizītes laikā" āķis. // Vienkāršības labad, nedaudz pārstrukturēsim drukāšanas loģiku. // Vai labāk, izveidosim veltītu PrintWalker. Pagaidām pieturēsimies pie pirms/pēc un parādīsim novērtēšanu, kas ir labāk piemērota. std::cout << "\n--- Novērtēšanas operācija --- "; std::vector<double> eval_stack; auto eval_pre_visit = [](const auto&){}; // Neko nedarīt pirms vizītes auto eval_post_visit = Overloaded { [&](const NumberNode& node) { eval_stack.push_back(node.value); }, [&](const UnaryOpNode& node) { double operand = eval_stack.back(); eval_stack.pop_back(); eval_stack.push_back(-operand); }, [&](const BinaryOpNode& node) { double right = eval_stack.back(); eval_stack.pop_back(); double left = eval_stack.back(); eval_stack.pop_back(); switch(node.op) { case BinaryOpNode::Operator::Add: eval_stack.push_back(left + right); break; case BinaryOpNode::Operator::Subtract: eval_stack.push_back(left - right); break; case BinaryOpNode::Operator::Multiply: eval_stack.push_back(left * right); break; case BinaryOpNode::Operator::Divide: eval_stack.push_back(left / right); break; } } }; auto evaluator = make_tree_walker(eval_pre_visit, eval_post_visit); std::visit(evaluator, root->var); std::cout << "Novērtēšanas rezultāts: " << eval_stack.back() << std::endl; return 0; }
Apskatiet novērtēšanas loģiku. Tā ir ideāli piemērota pēc secības pārejai. Mēs veicam operāciju tikai pēc tam, kad tās bērnu vērtības ir aprēķinātas un ievietotas steka. `eval_post_visit` lambda tver `eval_stack` un satur visu novērtēšanas loģiku. Šī loģika ir pilnībā atdalīta no mezglu definīcijām un `TreeWalker`. Mēs esam panākuši skaistu trīskāršu atbildību atdalīšanu: datu struktūra (Mezgli), pārejas algoritms (`TreeWalker`) un darbības loģika (lambdas).
Vispārīgā apmeklētāja pieejas priekšrocības
Šī implementācijas stratēğija sniedz ievērojamas priekšrocības, īpaši liela mēroga, ilgstošos programmatūras projektos.
Nepārspējama elastība un paplašināmība
Tā ir galvenā priekšrocība. Jaunas operācijas pievienošana ir triviāla. Jūs vienkārši uzrakstāt jaunu lambdu kopu un nododat to `TreeWalker`. Jūs nepieskaraties nevienam esošajam kodam. Tas pilnībā atbilst Atklātības/Slēgtības principam. Jauna mezglu tipa pievienošana prasa struktūras pievienošanu un `std::variant` alias atjaunināšanu — viena, lokalizēta izmaiņa — un pēc tam apmeklētāju atjaunināšanu, kuriem tā ir jāapstrādā. Kompilators jums laipni paziņos, kuri apmeklētāji (pārslodzīti lambdas) tagad trūkst pārslodzes.
Izcila atbildību atdalīšana
Mēs esam izolējuši trīs atšķirīgas atbildības:
- Datu attēlojums: `Node` struktūras ir vienkārši, neaktīvi datu konteineri.
- Pārejas mehānika: `TreeWalker` klase vienīgi pieder loģikai, kā virzīties pa koku struktūru. Jūs varētu viegli izveidot `InOrderTreeWalker` vai `BreadthFirstTreeWalker`, nemainot nevienu citu sistēmas daļu.
- Darbības loģika: pārejas laikā nodotās lambdas satur konkrēto biznesa loģiku attiecīgajam uzdevumam (novērtēšanai, drukāšanai, tipu pārbaudei utt.).
Šī atdalīšana padara kodu vieglāk saprotamu, testējamu un uzturamu. Katrai sastāvdaļai ir viena, labi definēta atbildība.
Uzlabota atkārtota lietošana
The `TreeWalker` ir bezgalīgi atkārtoti lietojams. Pārejas loģika tiek rakstīta vienreiz un to var piemērot neierobežotam skaitam operāciju. Tas samazina koda dublēšanos un kļūdu potenciālu, kas var rasties, atkārtoti implementējot pārejas loģiku katrā jaunajā apmeklētājā.
Kodolīgs un izteiksmīgs kods
Ar modernām C++ funkcijām iegūtais kods bieži ir kodolīgāks nekā klasiskās apmeklētāju implementācijas. Lambdas ļauj definēt darbības loģiku tieši tur, kur tā tiek izmantota, kas var uzlabot lasāmību vienkāršām, lokalizētām operācijām. `Overloaded` palīgstruktūra apmeklētāju izveidei no lambdu kopas ir izplatīta un jaudīga idioma, kas saglabā apmeklētāju definīcijas tīras.
Potenciālās kompromisi un apsvērumi
Neviens modelis nav sudraba lode. Ir svarīgi saprast saistītos kompromisus.
Sākotnējās iestatīšanas sarežğītība
`Node` struktūras sākotnējā iestatīšana ar `std::variant` un vispārīgo `TreeWalker` var šķist sarežğītāka nekā vienkārša rekursīva funkcijas izsaukšana. Šis modelis sniedz vislielāko labumu sistēmās, kurās koku struktūra ir stabila, bet operāciju skaits, kā paredzams, laika gaitā pieaugs. Ļoti vienkāršiem, vienreizējiem koku apstrādes uzdevumiem tas varētu būt pārmērīgs.
Veiktspēja
Šī modeļa veiktspēja C++ programmēšanas valodā, izmantojot `std::visit`, ir izcila. `std::visit` parasti kompilatori ieviesto, izmantojot ļoti optimizētu pārslēgšanas tabulu, padarot pāreju ārkārtīgi ātru — bieži vien ātrāku nekā virtuālās funkciju izsaukšana. Citās valodās, kurām, lai panāktu līdzīgu vispārīgu uzvedību, var būt nepieciešama atspoguļošana vai vārdnīcās balstīta tipu meklēšana, var būt ievērojama veiktspējas virslieta, salīdzinot ar klasisku, statiski pārvietotu apmeklētāju.
Valodu atkarība
Šīs konkrētās implementācijas elegance un efektivitāte ir ļoti atkarīga no C++17 funkcijām. Lai gan principi ir pārnesami, implementācijas detaļas citās valodās atšķirsies. Piemēram, Java, iespējams, izmantos noslēgtu interfeisu un pattern matching mūsdienu versijās vai vairāk aprakstošu map-based dispatcher vecākās versijās.
Reālās pasaules lietojumprogrammas un lietošanas gadījumi
Vispārīgais apmeklētāja dizaina modelis koku pārejai nav tikai akadēmisks vingrinājums; tas ir daudzu sarežğītu programmatūras sistēmu mugurkauls.
- Kompilatori un interpretori: Tas ir kanonisks lietošanas gadījums. Abstraktās sintakses koku (AST) vairākas reizes apmeklē dažādas "apmeklētāji" vai "pārejas". Semantiskās analīzes pāreja pārbauda tipu kļūdas, optimizācijas pāreja pārraksta koku, lai tas būtu efektīvāks, un koda ğenerēšanas pāreja apmeklē gala koku, lai izvadītu mašīnkodu vai baitkodu. Katra pāreja ir atšķirīga operācija ar vienu un to pašu datu struktūru.
- Statiskās analīzes rīki: Rīki, piemēram, linteri, koda formatētāji un drošības skeneri, analizē kodu AST un pēc tam palaida uz tā dažādus apmeklētājus, lai atrastu modeļus, ieviestu stila noteikumus vai atklātu potenciālas ievainojamības.
- Dokumentu apstrāde (DOM): Kad jūs manipulējat ar XML vai HTML dokumentu, jūs strādājat ar koku. Visu saišu izgūšanai, visu attēlu transformēšanai vai dokumenta serializēšanai citā formātā var izmantot vispārīgu apmeklētāju.
- UI sistēmas: Mūsdienu UI sistēmas attēlo lietotāja saskarni kā komponentu koku. Šī koka pāreja ir nepieciešama renderēšanai, stāvokļa atjauninājumu izplatīšanai (kā React atjaunošanas algoritmā) vai notikumu izsaukšanai.
- 3D grafikas ainu grafiki: 3D aina bieži tiek attēlota kā objektu hierarhija. Pāreja ir nepieciešama, lai lietotu transformācijas, veiktu fizikas simulācijas un iesniegtu objektus renderēšanas cauruļvadam. Vispārīgais pārejas rīks varētu lietot renderēšanas operāciju, tad tikt atkārtoti izmantots, lai lietotu fizikas atjaunināšanas operāciju.
Secinājums: Jauns abstrakcijas līmenis
Vispārīgais apmeklētāja dizaina modelis, īpaši, ja tas ir ieviests ar īpašu `TreeWalker`, ir spēcīga programmatūras dizaina evolūcija. Tā ņem oriğinālo apmeklētāja dizaina modeļa solījumu — datu un operāciju atdalīšana — un paaugstina to, arī atdalot sarežğīto pārejas loģiku.
Sadalot problēmu trīs atsevišķās, ortogonālās sastāvdaļās — datos, pārejā un operācijā — mēs veidojam sistēmas, kas ir modulārākas, vieglāk uzturamas un izturīgākas. Spēja pievienot jaunas operācijas, nemodificējot pamatdatu struktūras vai pārejas kodu, ir monumentāla uzvara programmatūras arhitektūrai. `TreeWalker` kļūst par atkārtoti lietojamu aktīvu, kas var darbināt desmitiem funkciju, nodrošinot, ka pārejas loģika ir konsekventa un pareiza visur, kur tā tiek izmantota.
Lai gan tas prasa sākotnēju ieguldījumu izpratnē un iestatīšanā, vispārīgais koku pārejas apmeklētāja dizaina modelis atmaksājas visa projekta dzīves laikā. Jebkuram izstrādātājam, kas strādā ar sarežğītiem hierarhiskiem datiem, tas ir nepieciešams rīks, lai rakstītu tīru, elastīgu un izturīgu kodu.